package net.tngou.db.lucene;



import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import java.util.Map.Entry;
import java.util.Set;

import net.tngou.db.entity.Field;
import net.tngou.db.entity.Page;
import net.tngou.db.i18n.I18N;
import net.tngou.db.util.ResultSet;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiPhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wltea.analyzer.lucene.IKAnalyzer;






/**
 * 
* @ClassName: LuceneManage
* @Description:  Lucene 操作管理
* @author tngou@tngou.net (www.tngou.net)
* @date 2015年5月20日 下午2:56:11
*
 */
public class LuceneManage {

	
	private static Logger log = LoggerFactory.getLogger(LuceneManage.class);
	private static LuceneManage  luceneManage= null;
	private static Config CONFIG=null;      //Lucene数据存储引擎配置
	private static Analyzer analyzer=null;  //分词气
	IndexWriterConfig config  = null; 
	private static  String DEFAULT_TABLE = "";//默认表
	/**
	 * 单例方法
	 * @return
	 */
	public final static LuceneManage getInstance (){
		if(luceneManage==null)
		{
		   return new LuceneManage();
		}else
		{
			return luceneManage;
		}
	}
	
	private LuceneManage()
	{
		CONFIG=Config.getInstance();
		DEFAULT_TABLE=CONFIG.getTable();
		analyzer = new IKAnalyzer();
		config = new IndexWriterConfig(analyzer);
	}
	
	
	/**
	 *  
	* @Title: createTable
	* @Description: 创建表
	* @param @param tableName  表名称
	* @return void    返回类型
	* @throws
	 */
	public boolean  createTable(String tableName) {
		
		boolean r=true;
		File path= new File(CONFIG.getPath());
		if(!path.isDirectory())
		{
			path.mkdirs();
		}
		if(!path.canRead())
		{
			path.setExecutable(true);//设置可执行权限  
			path.setReadable(true);//设置可读权限  
			path.setWritable(true);//设置可写权限 
		}
		
						
		File docDir= new File(CONFIG.getPath(), tableName);
		if(docDir.isDirectory())
		{
			
			r=false;
		}
		else
		{
			docDir.mkdirs();
			
		}
		
		if(!docDir.canRead())
		{
			docDir.setExecutable(true);//设置可执行权限  
			docDir.setReadable(true);//设置可读权限  
			docDir.setWritable(true);//设置可写权限 
		}
		
		
		return r;
	}
	
	/**
	 * 
	* @Title: renameTable
	* @Description: 表重新命名
	* @param @param old_tableName  原来表名称
	* @param @param new_tableName  现在表名称
	* @param @return    设定文件
	* @return boolean    返回类型
	* @throws
	 */
	public boolean renameTable(String old_tableName,String new_tableName) {
		boolean r=true;
		File oldDir= new File(CONFIG.getPath(), old_tableName);
		if(!oldDir.isDirectory())
		{
			return false;
		}
		File newDir= new File(CONFIG.getPath(), new_tableName);
		if(newDir.isDirectory())
		{
			return false;
		}
		oldDir.renameTo(newDir);
		return r;
		
	}
	
	
	/**
	 * 
	* @Title: existTable
	* @Description: 参考表是否存在
	* @param @param tableName 表名
	* @param @return    是否存在
	* @return boolean    返回类型
	* @throws
	 */
	public boolean existTable(String tableName) {
		File dir= new File(CONFIG.getPath(), tableName);
		return dir.exists();
	}
	
	
	/**
	 * 
	* @Title: dropTable
	* @Description: 删除表
	* @param @param tableName    删除表
	* @return void    返回类型
	* @throws
	 */
	public boolean dropTable(String tableName) {
		
		boolean r=true;		
		File docDir= new File(CONFIG.getPath(), tableName);
		if(docDir.isDirectory())
		{			
				try {
					FileUtils.deleteDirectory(docDir);
					
				} catch (IOException e) {
					r=false;
					
				}			
		}else
		{
			r=false;
		}
		
		return r;
		
		
	
		
	}
	
	
	/**
	 * 
	* @Title: clearTable
	* @Description: 清除数据
	* @param @param tableNames    设定文件
	* @return void    返回类型
	* @throws
	 */
	public void clearTable(String tableName) {
	
		dropTable(tableName); //删除表
		createTable( tableName);//创建表
	}
	
	
	/**
	 * 
	* @Title: delete
	* @Description: 按照主键删除数据，这里只允许通过主键的方式来删除内容
	* @param @param tableName  表名
	* @param @param field    删除条件
	* @return  int   返回类型   状态码
	* @throws
	 */
	public int delete(Field field,String tableName) {
		
		if(!existTable(tableName)) return ResultSet.T_ERROR_READ; //406,访问的表不存在	
		File indexPath= new File(CONFIG.getPath(), tableName);
		Directory directory=null;
		IndexWriter iwriter=null;
		try {			
			 directory = FSDirectory.open(Paths.get(indexPath.toURI()));
			 iwriter = _IndexWriter(directory, tableName);
			 iwriter.deleteDocuments(new Term(field.getName(), field.getValue())); //删除主键
			 iwriter.commit();
		
		} catch (IOException e) {
			log.error(I18N.getValue("delete_error",tableName));	
			return ResultSet.T_FAILURE_SYSTEM; //系统Lucene错误 500
		}
		finally
		{			
			try {
				if(iwriter!=null)iwriter.close(); //关闭文件锁
				if(directory!=null)directory.close();	
			} catch (IOException e) {	
				log.error(I18N.getValue("close_error",tableName));
				return ResultSet.T_FAILURE_SYSTEM;//系统Lucene错误 500
			}
			
		}
		
		return ResultSet.T_SUCCESS_OK; //200成功状态码
	}
	
	
	/**
	 * 
	* @Title: add
	* @Description: 添加数据，数据插入
	* @param @param list  添加属性列
	* @param @param tableName 添加到表
	* @param @return   
	* @return int    返回状态码
	* @throws
	 */
	public   int add( List<Field> list,String tableName) {
		
		if(!existTable(tableName)) return ResultSet.T_ERROR_READ; //406,访问的表不存在	
		Directory directory=null;
		IndexWriter iwriter=null;
		File indexPath= new File(CONFIG.getPath(), tableName);		 
		try {

			 directory = FSDirectory.open(Paths.get(indexPath.toURI())); 
			 iwriter = _IndexWriter(directory, tableName); 
			 Document doc = new Document();
			 for (Field field : list) {	
				 
				if(field.getType().equals(Field.Type.Key))
				{	
					iwriter.deleteDocuments(new Term(field.getName(), field.getValue())); //删除主键
					doc.add(new StringField(field.getName(), field.getValue(),Store.YES)); //不用分词 
					
				}else if(field.getType().equals(Field.Type.String))
				{
					doc.add(new StringField(field.getName(), field.getValue(),Store.YES)); //不用分词 
				}else if(field.getType().equals(Field.Type.Text))
				{
					doc.add(new TextField(field.getName(), field.getValue(), Store.YES)); //对String进行分词存储
				}					
			}		
			iwriter.addDocument(doc);
			iwriter.commit();	
			
		} catch (IOException e) {
			
			log.error(I18N.getValue("add_error",tableName));	
			return ResultSet.T_FAILURE_SYSTEM; //系统Lucene错误 500
			
		}
		finally
		{	
			try {
				if(iwriter!=null)iwriter.close();
				if(directory!=null)directory.close();		
			} catch (IOException e) {
				log.error(I18N.getValue("close_error",tableName));
				return ResultSet.T_FAILURE_SYSTEM;//系统Lucene错误 500
			}		
			
		}
		return ResultSet.T_SUCCESS_OK; //200成功状态码
	}
	
	
	
	 /**
	  * 
	 * @Title: update
	 * @Description: 更新数据
	 * @param @param tableName  表名
	 * @param @param term  修改的主键
	 * @param @param updates    修改的数据
	 * @return int    返回类型
	 * @throws
	  */
	public int update( String tableName,Field term,Field ...updates) {
		
		if(!existTable(tableName)) return ResultSet.T_ERROR_READ; //406,访问的表不存在	
		File indexPath= new File(CONFIG.getPath(), tableName);
		Directory directory=null;
		IndexWriter iwriter=null;
		try {
			 directory = FSDirectory.open(Paths.get(indexPath.toURI()));
			 iwriter = _IndexWriter(directory, tableName);
			 IndexSearcher isearcher = _getSearcher(tableName);
 			 
			 Query query= new TermQuery(new Term(term.getName(), term.getValue()));
			 TopDocs topDocs = isearcher.search(query, 1);
		
			 ScoreDoc[] list = topDocs.scoreDocs;
			 if(list.length>0)
			 {
				 Document doc = isearcher.doc(list[0].doc);
				
				 for (Field field : updates) {
					
						if(field.getType().equals(Field.Type.Key))
						{		
							doc.add(new StringField(field.getName(), field.getValue(),Store.YES)); //不用分词 
						}else if(field.getType().equals(Field.Type.String))
						{
							doc.add(new StringField(field.getName(), field.getValue(),Store.YES)); //不用分词 
						}else if(field.getType().equals(Field.Type.Text))
						{
							doc.add(new TextField(field.getName(), field.getValue(), Store.YES)); //对String进行分词存储
						}
				
				}
				  
				iwriter.updateDocument(new Term(term.getName(), term.getValue()), doc);	
				iwriter.commit();
				 
			 }
		
		
		} catch (IOException e) {
			log.error(I18N.getValue("update_error",tableName));	
			return ResultSet.T_FAILURE_SYSTEM; //系统Lucene错误 500
		}
		finally
		{
				
			try {
				if(iwriter!=null)iwriter.close();
				if(directory!=null)directory.close();
				
			} catch (IOException e) {
				log.error(I18N.getValue("close_error",tableName));
				return ResultSet.T_FAILURE_SYSTEM;//系统Lucene错误 500
			}			
		}
		return ResultSet.T_SUCCESS_OK; //200成功状态码
	}

	
	
	/**
	 * 
	* @Title: get
	* @Description: 驱动数据
	* @param @param tableNames 表
	* @param @param field  取得一个数据烈
	* @return Map<String,Object>    返回类型
	* @throws
	 */
	public Map<String, Object> get(Field field,String ... tableNames)  {
		
		Map<String, Object> map = new HashMap<String, Object>();
		try {
			 
			 IndexSearcher isearcher = _getSearcher(tableNames);			 			 
			 Query query= new TermQuery(new Term(field.getName(), field.getValue()));
			 TopDocs topDocs = isearcher.search(query, 1);//取得一条数据
			 ScoreDoc[] list = topDocs.scoreDocs;
		
			 if(list.length>0)
			 {				 
				 Document doc = isearcher.doc(list[0].doc);
				 List<IndexableField> fields = doc.getFields();
				 fields.forEach(e->{
					 map.put(e.name(),  e.stringValue());		 
				 });
			 }

			 
		 } catch (IOException    e) {
			 log.error(I18N.getValue("select_error"));

				
		}
		
		return map;
	}
	
	
	

	
	/**
	 * 
	* @Title: get
	* @Description: 取得数据
	* @param @param tableName
	* @param @param start
	* @param @param size
	* @param @param field
	* @param @param sortField
	* @param @return    设定文件
	* @return Page    返回类型
	* @throws
	 */
	public Page get(int start,int size,Field field,SortField[] sortField,String ... tableNames) {
		
	
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		int total=0;
		
		try {
			 			
			 IndexSearcher isearcher = _getSearcher(tableNames);
			 Query query= new TermQuery(new Term(field.getName(),field.getValue()));
			 TopDocs topDocs=null;
				if(sortField==null)  //排序字段为空 ，也就Top排序
				{
					topDocs=isearcher.search(query, CONFIG.getSize());
				}else
				{
					topDocs=isearcher.search(query, CONFIG.getSize(), new Sort(sortField));
				}
			 ScoreDoc[] docs = topDocs.scoreDocs;
		     total =docs.length;
			 
			for (int i = start; i < docs.length&&i<(start+size); i++) {	 
				Map<String, Object> map = new HashMap<String, Object>();
				Document doc = isearcher.doc(docs[i].doc);
				 List<IndexableField> fields = doc.getFields();
				 fields.forEach(e->{
					 map.put(e.name(),  e.stringValue());		 
				 });
				
				 list.add(map);
			 }
			 		 

		 } catch (IOException    e) {
			   log.error(I18N.getValue("select_error"));
			   return new Page(ResultSet.T_ERROR_SQL);//400 请求错误，语法错误
		
		}
		
		return new Page(start, size ,total, list);
		
	}
	
	
	/**
	 * 
	* @Title: get
	* @Description: 取得数据
	* @param @param tableName
	* @param @param start
	* @param @param size
	* @param @param reverse
	* @param @return    设定文件
	* @return Page    返回类型
	* @throws
	 */
	public Page get(int start,int size, boolean reverse,String  tableName) {
		
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		int total=0;
		File indexPath= new File(CONFIG.getPath(), tableName);
		
		 try {
			 Directory directory = FSDirectory.open(Paths.get(indexPath.toURI()));
			
			 IndexReader ireader = DirectoryReader.open(directory);
				total = ireader.maxDoc();
					 
				 for (int i = start; i < total&&i<(start+size); i++) {
						Map<String, Object> map = new HashMap<String, Object>();
						int idoc=reverse?(total-(i+1)):i;
						
						Document doc = ireader.document(idoc );

						 List<IndexableField> fields = doc.getFields();
						 fields.forEach(e->{
							 map.put(e.name(),  e.stringValue());		
						 });
						 list.add(map);
						
					}
				 
				 	
			 ireader.close(); 
		 } catch (IOException    e) {
			 log.error(I18N.getValue("select_error"));
			 return new Page(ResultSet.T_ERROR_SQL);//400 请求错误，语法错误
				
			}
		 return new Page(start, size, total,list);
	}
	
	
	
	/**
	 * 
	* @Title: get
	* @Description: 取得数据
	* @param @param tableName
	* @param @param start
	* @param @param size
	* @param @param fields
	* @param @param sortField
	* @param @return    设定文件
	* @return Page    返回类型
	* @throws
	 */
	public Page get(int start,int size,Field[] fields,SortField[] sortField,String ... tableNames) {
		
		
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		int total=0;

		try {		
			 IndexSearcher isearcher =_getSearcher(tableNames);		
			 MultiPhraseQuery query = new MultiPhraseQuery() ;
			 Term[] terms= new Term[fields.length];
			 for (int i=0;i<fields.length;i++) {
				 terms[i] = new Term(fields[i].getName(),fields[i].getValue());
			}
			query.add(terms);
			TopDocs topDocs=null;
			if(sortField==null)  //排序字段为空 ，也就Top排序
			{
				topDocs=isearcher.search(query, CONFIG.getSize());
			}else
			{
				topDocs=isearcher.search(query, CONFIG.getSize(), new Sort(sortField));
			}
			 ScoreDoc[] docs = topDocs.scoreDocs;
		   total =docs.length;
			 
			for (int i = start; i < docs.length&&i<(start+size); i++) {
				 
				Map<String, Object> map = new HashMap<String, Object>();
				Document doc = isearcher.doc(docs[i].doc);
				 List<IndexableField> indexfields = doc.getFields();
				 indexfields.forEach(e->{
					 map.put(e.name(),  e.stringValue());
				 });
				 list.add(map);
			 }
			 

		 } catch (IOException    e) {
			 log.error(I18N.getValue("select_error"));
			 return new Page(ResultSet.T_ERROR_SQL);//400 请求错误，语法错误
		}
		
		return new Page(start, size, total, list);
		
	}
	
	
	/**
	 * 
	* @Title: get
	* @Description: 取得数据
	* @param @param tableName
	* @param @param start
	* @param @param size
	* @param @param fields
	* @param @param flags
	* @param @param sortField
	* @param @return    设定文件
	* @return Page    返回类型
	* @throws
	* 
	*  String[] fields = {"filename", "contents", "description"};
 			BooleanClause.Occur[] flags = {BooleanClause.Occur.SHOULD,
                BooleanClause.Occur.MUST,
                BooleanClause.Occur.MUST_NOT};
            (filename:query) +(contents:query) -(description:query)     
            
            SHOULD:分词搜索
            MUST:必须
            MUST_NOT: 必须没有  
	 */
	public Page get(int start,int size,Field[] fields,BooleanClause.Occur[] flags,SortField[] sortField,String ... tableNames) {
		
		
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		int total=0;
		try {					
			 IndexSearcher isearcher = _getSearcher(tableNames);		 
			String[] queries = new String[fields.length];
			String[] sfield   = new String[fields.length];
			for (int i = 0; i < fields.length; i++) {
				queries[i]=fields[i].getValue();
				sfield[i]=fields[i].getName();
			}
			Query query = MultiFieldQueryParser.parse(queries, sfield, flags, analyzer);
			
			TopDocs topDocs=null;
			if(sortField==null)  //排序字段为空 ，也就Top排序
			{
				topDocs=isearcher.search(query, CONFIG.getSize());
			}else
			{
				topDocs=isearcher.search(query, CONFIG.getSize(), new Sort(sortField));
			}
			ScoreDoc[] docs = topDocs.scoreDocs;
		   total =docs.length;
			 
			for (int i = start; i < docs.length&&i<(start+size); i++) {
				 
				Map<String, Object> map = new HashMap<String, Object>();
				Document doc = isearcher.doc(docs[i].doc);
				
				 List<IndexableField> indexfields = doc.getFields();
				 indexfields.forEach(e->{									
					 map.put(e.name(),  e.stringValue());							 
				 });
				 list.add(map);
			 }
			  
		 } catch (IOException | ParseException    e) {
			 log.error(I18N.getValue("select_error"));
			 return new Page(ResultSet.T_ERROR_SQL);//400 请求错误，语法错误
		}
		
		return new Page(start, size, total, list);
		
	}
	
	
	
	/**
	 * 
	* @Title: get
	* @Description: 取得数据
	* @param @param tableName
	* @param @param start
	* @param @param size
	* @param @param fields
	* @param @param querie
	* @param @param flags
	* @param @param sortField
	* @param @return    设定文件
	* @return Page    返回类型
	* @throws
	 */
	public Page get(int start,int size,String[] fields,String querie,BooleanClause.Occur[] flags,SortField[] sortField,String ... tableNames) {
		
		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		int total=0;
		try {
			 
			
			 IndexSearcher isearcher = _getSearcher(tableNames);	
			Query query = MultiFieldQueryParser.parse(querie, fields, flags, analyzer);
			
			TopDocs topDocs=null;
			if(sortField==null)  //排序字段为空 ，也就Top排序
			{
				topDocs=isearcher.search(query, CONFIG.getSize());
			}else
			{
				topDocs=isearcher.search(query, CONFIG.getSize(), new Sort(sortField));
			}
			
			 ScoreDoc[] docs = topDocs.scoreDocs;
		   total =docs.length;
			 
			for (int i = start; i < docs.length&&i<(start+size); i++) {
				 
				Map<String, Object> map = new HashMap<String, Object>();
				Document doc = isearcher.doc(docs[i].doc);
				 List<IndexableField> indexfields = doc.getFields();
				 indexfields.forEach(e->{
					 IndexableField indexablefield=e;
					 map.put(indexablefield.name(),  indexablefield.stringValue());
					
					 
				 });
				 list.add(map);
			 }
			 

		 } catch (IOException | ParseException    e) {
			 log.error(I18N.getValue("select_error"));
			 return new Page(ResultSet.T_ERROR_SQL);//400 请求错误，语法错误
		}
		
		return new Page(start, size, total, list);
		
	}
	
	

	
	/**
	 * 
	* @Title: _getSearcher
	* @Description: 取得索引信息
	* @param @param tableNames
	* @param @return
	* @param @throws IOException    设定文件
	* @return IndexSearcher    返回类型
	* @throws
	 */
	private IndexSearcher _getSearcher(String ... tableNames) throws IOException {
		if(tableNames==null)
		{
			tableNames=new String[1];
			tableNames[0]=DEFAULT_TABLE;
		}
		IndexReader[] readers = new IndexReader[tableNames.length];
		int idx = 0;
		for (String tableName : tableNames) {
			File indexPath= new File(CONFIG.getPath(), tableName);
			FSDirectory dir = FSDirectory.open(Paths.get(indexPath.toURI()));
//			 if(_unLock(tableName))//判断表锁定 ，，等待表解锁
			readers[idx++] = DirectoryReader.open(dir);
			
		}
		IndexSearcher isearcher = new IndexSearcher(new MultiReader(readers, true));
		return isearcher;
	}

	
	
	public synchronized static boolean IsLock(String tableName) {
		
		
		try {
			CONFIG=Config.getInstance();
			File indexPath= new File(CONFIG.getPath(), tableName);
			Directory directory = FSDirectory.open(Paths.get(indexPath.toURI()));
			return IndexWriter.isLocked(directory);
		 
		} catch (IOException e) {
			
			e.printStackTrace();
			return false;
		}
		
	
	}
	
	
	private synchronized static  boolean _unLock(String tableName) {
		
		
		try {
			CONFIG=Config.getInstance();
			File indexPath= new File(CONFIG.getPath(), tableName);
			Directory directory = FSDirectory.open(Paths.get(indexPath.toURI()));
			while (IndexWriter.isLocked(directory)) {
				
				// 等待解锁
			} 
			return true;
		 
		} catch (IOException e) {
			
			e.printStackTrace();
			return false;
		}
		
	
	}
	
	
	private IndexWriter _IndexWriter( Directory directory,String tableName) {
		
		IndexWriter iwriter=null;
		try {
			if(_unLock(tableName))//判断表锁定 ，，等待表解锁
			iwriter = new IndexWriter(directory, config);
		} catch (IOException e) {
			
//			e.printStackTrace();
			log.error("{} is Lock!",tableName);
			_IndexWriter( directory, tableName);
		}
//		if(iwriter==null)return _IndexWriter( directory, tableName);
		return iwriter;
		
		
	}
	
	@SuppressWarnings("unused")
	private void _print(Map<String, Object> map) {
		
		Set<Entry<String, Object>> sets = map.entrySet();
		for (Entry<String, Object> entry : sets) {
			System.err.println(entry.getKey()+":"+entry.getValue());
		}
		
		System.err.println("**************************************");
	}
	
	
}
